﻿using MongoDB.Driver;
using SimpleInjector;
using Sustainalytics.MongoDB.Extensions;
using Sustainalytics.Synchronization.Interface;
using Sustainalytics.Synchronization.Validation;
using Sustainalytics.Synchronization.Validation.Handlers.Common;
using Sustainalytics.Synchronization.Validation.Handlers.Comprehensive;
using Sustainalytics.Synchronization.Validation.Handlers.Core;
using Sustainalytics.Synchronization.Validation.Handlers.CorporateGovernance;
using Sustainalytics.Synchronization.Validation.Handlers.Events;
using Sustainalytics.Synchronization.Validation.Internals;
using Sustainalytics.Synchronization.Validation.ValidationRules.Common;
using Sustainalytics.Synchronization.Validation.ValidationRules.CorporateGovernance;
using System;
using System.Collections.Generic;
using System.Configuration;
using System.Linq;
using System.Threading.Tasks;

namespace Sustainalytics.Synchronizations.ValidationSyncStep
{
    public class ValidationSyncStep : ISyncStep
    {
        public SyncStepOrder ExecutionOrder => SyncStepOrder.DataValidation;

        public SyncContext Start(SyncContext syncContext)
        {
            try
            {
                syncContext.Token.ThrowIfCancellationRequested();
                syncContext.Log("Begin sync ValidationSyncStep");

                var result = ValidateAsync(syncContext).Result;

                if (PerformRestore())
                {
                    RestoreProfilesAsync(syncContext, result).Wait();
                    RecalculateProfiles(syncContext, result);
                }

                syncContext.Token.ThrowIfCancellationRequested();
                syncContext.Log("End sync ValidationSyncStep");

                syncContext.Status = SyncOperationStatus.Succeeded;
            }
            catch (AggregateException ex)
            {
                syncContext.Log(ex.Flatten().ToString());
                syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }
            catch (Exception ex)
            {
                syncContext.Log(ex.ToString());
                syncContext.Status = SyncOperationStatus.Failed;
                throw;
            }

            return syncContext;
        }

        private static bool PerformRestore()
        {
            var strPerformRestore =
                System.Configuration.Abstractions.ConfigurationManager.Instance.AppSettings.Get("performProfileRestore");
            try
            {
                return bool.Parse(strPerformRestore);
            }
            catch
            {
                return true;
            }
        }

        private static async Task InitalizeCorporateDataAsync(Container container)
        {
            var whiteListCompaniesLoader = container.GetInstance<WhiteListCompaniesLoader>();
            await whiteListCompaniesLoader.LoadAsync().ConfigureAwait(false);

            var comprehensiveCorporateDataLoader = container.GetInstance<ComprehensiveCorporateDataLoader>();
            await comprehensiveCorporateDataLoader.LoadAsync().ConfigureAwait(false);

            var coreCorporateDataLoader = container.GetInstance<CoreCorporateDataLoader>();
            await coreCorporateDataLoader.LoadAsync().ConfigureAwait(false);

            var controversyCorporateDataLoader = container.GetInstance<ControversyCorporateDataLoader>();
            await controversyCorporateDataLoader.LoadAsync().ConfigureAwait(false);

            var governanceDataLoader = container.GetInstance<GovernanceCorporateDataLoader>();
            await governanceDataLoader.LoadAsync().ConfigureAwait(false);
        }

        private static async Task<InvalidCompaniesList> ValidateAsync(SyncContext context)
        {
            var validationConnectionString = ConfigurationManager.ConnectionStrings["ValidationRules"].ConnectionString;

            InitializeDatabase(validationConnectionString);

            var container =
                await ValidationCompositionRoot.RegisterAsync(validationConnectionString, context).ConfigureAwait(false);
            await InitializeHashSetsAsync(container).ConfigureAwait(false);
            await InitalizeCorporateDataAsync(container).ConfigureAwait(false);
            var validationEngine = container.GetInstance<ValidationEngine>();
            return await validationEngine.ValidateAsync().ConfigureAwait(false);
        }

        private static async Task RestoreProfilesAsync(SyncContext context, InvalidCompaniesList result)
        {
            var container = ProfileRestoreCompositionRoot.Register(context);
            var restoreEngine = container.GetInstance<ProfileRestoreEngine>();
            await restoreEngine.RestoreAsync(result).ConfigureAwait(false);
        }

        private static async Task InitializeHashSetsAsync(Container container)
        {
            var hashSetLoader = container.GetInstance<IHashSetLoader>();

            await hashSetLoader.LoadAsync().ConfigureAwait(false);
        }

        private static void RecalculateProfiles(SyncContext context, InvalidCompaniesList result)
        {
            var associatedSyncSteps = new SyncStepsDictionary(context);
            var stepsToRun = new List<ISyncStep>();

            foreach (BusinessResearchProduct businessResearchProduct in Enum.GetValues(typeof(BusinessResearchProduct)))
            {
                var companiesIds = result.GetCompanies(businessResearchProduct);
                if (companiesIds.Any())
                {
                    var steps = associatedSyncSteps.GetSteps(businessResearchProduct);
                    var stepsToAdd = steps.Where(s => stepsToRun.All(x => x.ExecutionOrder != s.ExecutionOrder));
                    if (stepsToAdd.Any())
                    {
                        stepsToRun.AddRange(stepsToAdd);
                    }
                    if (steps.Any(s => s.ExecutionOrder == SyncStepOrder.ESGRatingsDataValidation))
                    {
                        var toRemove = context.PreviousChanges.Where(i => i.AffectedObjectName == ChangedElement.DATA_SET_DELETE_SYNC_FAIL);
                        if (toRemove != null && toRemove.Any())
                        {
                            context.PreviousChanges = context.PreviousChanges.Except(toRemove);
                        }

                        context.ResetCurrentChanges();
                    }
                }
            }

            context = stepsToRun.Aggregate(context, (current, step) => step.Start(current));
        }

        private static void InitializeDatabase(string connectionString)
        {
            var validationCollection = MongoFactory.GetCollectionFromDatabase<RuleMetadata>(connectionString, "Validators");
            validationCollection.DeleteMany(FilterDefinition<RuleMetadata>.Empty);

            var rulesList = new RulesList();

            foreach (var rule in rulesList.GetAllRules())
            {
                Type constructedType = typeof(FullNameDiscriminatorClassMap<>).MakeGenericType(rule.GetType());
                Activator.CreateInstance(constructedType, null);
                validationCollection.InsertOne((RuleMetadata)Activator.CreateInstance(rule.GetType(), null));
            }
        }
    }
}
